﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;

namespace Dycox.Validation
{
    public enum CompareOperator
    {
        Equals,
        NotEquals,
        GreaterThan,
        GreaterThanOrEquals,
        LessThan,
        LessThanOrEquals,
        StringContains,
        StringStartsWith,
        StringEndsWith,
        TypeEquals,
        TypeCompatible,
        TypeConvertible,
        BitTest
    }

    [Serializable]
    public class CompareValidator : Validator
    {
        public CompareValidator()
        {

        }

        //protected CompareValidator(SerializationInfo info, StreamingContext context)
        //    : base(info, context)
        //{
        //    _operator = (CompareOperator)info.GetValue("Operator", typeof(CompareOperator));
        //    string typeName = info.GetString("ValueType");
        //    Type type;
        //    if (typeName != null)
        //    {
        //        type = Type.GetType(typeName);
        //        if (type != null)
        //            _valueToCompare = info.GetValue("ValueToCompare", type);
        //    }
        //    typeName = info.GetString("ComparerType");
        //    if (typeName != null)
        //    {
        //        if ((type = Type.GetType(typeName)) != null)
        //            _comparer = 
        //    }
        //}

        //protected override void GetObjectData(SerializationInfo info, StreamingContext context)
        //{
        //    info.AddValue("Operator", _operator);
        //    if (_valueToCompare != null)
        //    {
        //        info.AddValue("ValueToCompare", _valueToCompare);
        //        info.AddValue("ValueType", _valueToCompare.GetType().AssemblyQualifiedName);
        //    }
        //    if (_comparer != null)
        //    {
        //        info.AddValue("ComparerType", _comparer.GetType().AssemblyQualifiedName);
        //    }
        //}

        protected internal override ValidationResult IsValid(object value, PropertyValidationContext context)
        {
            if (_valueToCompare == null)
                throw new InvalidOperationException("Must set ValueToCompare property before validating.");

            if (value == null) return ValidationResult.Ignore;

            Func<object, CompareOperator, PropertyValidationContext, bool> func;

            switch (_operator)
            {
                case CompareOperator.Equals:
                case CompareOperator.NotEquals:
                    if (_valueToCompare is string)
                        func = CheckString;
                    else
                        func = Compare;
                    break;
                case CompareOperator.GreaterThan:
                case CompareOperator.GreaterThanOrEquals:
                case CompareOperator.LessThan:
                case CompareOperator.LessThanOrEquals:
                    func = Compare;
                    break;
                case CompareOperator.StringContains:
                case CompareOperator.StringStartsWith:
                case CompareOperator.StringEndsWith:
                    func = CheckString;
                    break;
                case CompareOperator.TypeEquals:
                case CompareOperator.TypeCompatible:
                case CompareOperator.TypeConvertible:
                    func = CheckType;
                    break;
                case CompareOperator.BitTest:
                    func = BitTest;
                    break;
                default:
                    func = null;
                    break;
            }

            if (func != null)
            {
                bool valid;
                valid = func(value, _operator, context);
                return valid ? ValidationResult.Valid : ValidationResult.Invalid;
            }
            else
                return ValidationResult.Invalid;
        }

        private CompareOperator _operator;
        [DefaultValue(typeof(CompareOperator), "Equals")]
        public CompareOperator Operator
        {
            get { return _operator; }
            set { _operator = value; }
        }

        private bool CheckType(object value, CompareOperator op, PropertyValidationContext context)
        {
            Type typeToCompare = _valueToCompare as Type;
            Type type;

            if (value is Type) type = (Type)value;
            else type = value.GetType();

            if (typeToCompare == null)
                throw new InvalidOperationException("The comparing type is not correctly specified.");

            if (op == CompareOperator.TypeEquals)
                return type == typeToCompare;
            else if (op == CompareOperator.TypeCompatible)
                return typeToCompare.IsAssignableFrom(type);
            else if (op == CompareOperator.TypeConvertible)
            {
                TypeConverter converter = TypeDescriptor.GetConverter(typeToCompare);
                if (converter.CanConvertFrom(type))
                {
                    try
                    {
                        return converter.IsValid(value);
                    }
                    catch
                    {
                    }
                }
                return false;
            }

            throw new ArgumentException();
        }

        private bool BitTest(object value, CompareOperator op, PropertyValidationContext context)
        {
            int v0 = Dycox.Utils.ConvertUtil.ConvertType<int>(value, context, null);
            int v1 = Dycox.Utils.ConvertUtil.ConvertType<int>(_valueToCompare, context, null);

            return (v0 & v1) == v1;
        }

        private bool CheckString(object value, CompareOperator op, PropertyValidationContext context)
        {
            string text1 = Dycox.Utils.ConvertUtil.ConvertType<string>(value, context, null),
                text0 = Dycox.Utils.ConvertUtil.ConvertType<string>(_valueToCompare, context, null);

            StringComparer comparer = _comparer as StringComparer;
            StringComparison options = StringComparison.InvariantCulture;
            if (comparer != null && comparer.Compare("a", "A") == 0)
                options = StringComparison.InvariantCultureIgnoreCase;

            switch (op)
            {
                case CompareOperator.StringContains:
                    return text1.IndexOf(text0, options) >= 0;
                case CompareOperator.StringStartsWith:
                    return text1.StartsWith(text0, options);
                case CompareOperator.StringEndsWith:
                    return text1.EndsWith(text0, options);
                case CompareOperator.Equals:
                    return text1.Equals(text0, options);
                case CompareOperator.NotEquals:
                    return !text1.Equals(text0, options);
                default:
                    throw new ArgumentException();
            }

        }

        private IComparer _comparer;

        public IComparer Comparer
        {
            get { return _comparer; }
            set { _comparer = value; }
        }

        private bool Compare(object value, CompareOperator op, PropertyValidationContext context)
        {
            int diff;

            if (context != null && context.PropertyDescriptor != null)
                value = Dycox.Utils.ConvertUtil.ConvertType(value, context);
            else
                value = Dycox.Utils.ConvertUtil.ConvertType(value, _valueToCompare.GetType());

            if (_comparer != null)
                diff = _comparer.Compare(_valueToCompare, value);
            else if (_valueToCompare is IComparable)
                diff = ((IComparable)_valueToCompare).CompareTo(value);
            else
            {
                if (op == CompareOperator.NotEquals || op == CompareOperator.Equals)
                {
                    bool r = object.Equals(value, _valueToCompare);
                    if (op == CompareOperator.NotEquals)
                        r = !r;
                    return r;
                }
                else
                    throw new InvalidOperationException("Cannot compare values of type " + value.GetType().FullName);
            }

            switch (op)
            {
                case CompareOperator.Equals:
                    return diff == 0;
                case CompareOperator.NotEquals:
                    return diff != 0;
                case CompareOperator.GreaterThan:
                    return diff < 0;
                case CompareOperator.GreaterThanOrEquals:
                    return diff <= 0;
                case CompareOperator.LessThan:
                    return diff > 0;
                case CompareOperator.LessThanOrEquals:
                    return diff >= 0;
                default:
                    throw new ArgumentException();
            }

        }

        private object _valueToCompare;

        public object ValueToCompare
        {
            get { return _valueToCompare; }
            set { _valueToCompare = value; }
        }
    }
}
